﻿using System;
using System.Linq;
using System.Threading;
using System.Configuration;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Collections.Generic;
using System.Windows.Threading;

namespace MultiViewer.Network
{ 
    public abstract class Node
    {
        protected int nodeId;
        protected int nodeCount;
        protected bool inited = false;

        protected int[] allNodes = null;
        protected int[] clientNodes = null;

        private Dictionary<Type, Delegate> messageHandlers = new Dictionary<Type,Delegate>();
        private Dictionary<Type, Delegate> notificationHandlers = new Dictionary<Type, Delegate>();
        private Dictionary<Type, TransactionHandler> transactionHandlers = new Dictionary<Type, TransactionHandler>();

        private Dictionary<string, ITransactionState> transactions = new Dictionary<string, ITransactionState>();

        public event NodeConnectionHandler NodeConnected;
        public event NodeConnectionHandler NodeDisconnected;

        public static Node Start(params string[] args)
        {
            string nts = GetParameter("multiViewerNodeType","mvtype",args);
            if (nts == "server")
                return new ServerNode(args);
            else if (nts == "client")
                return new ClientNode(args);
            else
                throw new Exception("Node type is not specified neither in config nor in command line");
        }

        public virtual void  Stop()
        {
            inited = false;
        }

        /// <summary>Returns number of ongoing transactions</summary>
        public int TransactionCount
        {
            get { 
                lock(transactions)
                    return transactions.Count; 
            }
        }

        /// <summary>Gets node ID</summary>
        /// <remarks>Node ID is between 0 and NodeCount - 1</remarks>
        public int NodeID
        {
            get {
                if (!inited)
                    throw new InvalidOperationException("Cannot get node ID when network is not initialized");
                return nodeId; 
            }
        }

        /// <summary>Gets node count</summary>
        public int NodeCount
        {
            get
            {
                if (!inited)
                    throw new InvalidOperationException("Cannot get node count when network is not initialized");
                return nodeCount;
            }
        }

        protected static string GetParameter(string longName,string shortName,params string[] args)
        {
            string key = "/" + shortName + "=";
            if (args.Any(p => p.StartsWith(key)))
            {
                return args.Last(p => p.StartsWith(key)).Substring(shortName.Length + 2);
            }
            key = "/" + longName + "=";
            if (args.Any(p => p.StartsWith(key)))
            {
                return args.Last(p => p.StartsWith(key)).Substring(longName.Length + 2);
            }
            AppSettingsReader apr = new AppSettingsReader();
            try
            {
                return (string)apr.GetValue(longName, typeof(string));
            }
            catch // Value in config file is not found
            {
                return null; 
            }
        }

        public int[] AllNodes
        {
            get
            {
                if (allNodes == null)
                {
                    allNodes = new int[nodeCount];
                    for (int i = 0; i < nodeCount; i++)
                        allNodes[i] = i;
                }
                return allNodes;
            }
        }

        public int[] ClientNodes
        {
            get
            {
                if (clientNodes == null)
                {
                    clientNodes = new int[nodeCount-1];
                    for (int i = 1; i < nodeCount; i++)
                        clientNodes[i-1] = i;
                }
                return clientNodes;
            }
        }

        public abstract int[] ConnectedNodes
        {
            get;
        }

        public void AddMessageHandler<T>(MessageHandler<T> handler)
        {
            Type key = typeof(T);
            if (messageHandlers.Keys.Contains(key))
            {
                Trace.WriteLine("Message handler for " + key.Name + " is replaced");
                messageHandlers[key] = handler;
            } 
            else
                messageHandlers.Add(key, handler);
        }

        private delegate void SyncInvoker();

        public void AddMessageHandler<T>(Dispatcher dispatcher, AsyncMessageHandler<T> handler)
        {
            AddMessageHandler<T>(new MessageHandler<T>(delegate(T message)
            {
                HandlerResult<object> result = new HandlerResult<object>();
                dispatcher.BeginInvoke(new SyncInvoker(delegate()
                {
                    try
                    {
                        handler(message, result);
                    }
                    catch(Exception exc) 
                    {
                        result.SetFailure(exc);
                    }
                }));
                result.WaitForResult();
                return result.Value;
            }));
        }

        public void AddNotificationHandler<T>(NotificationHandler<T> handler)
        {
            Type key = typeof(T);
            Delegate oldHandler;
            if (notificationHandlers.TryGetValue(key, out oldHandler))
                notificationHandlers[key] = Delegate.Combine(oldHandler, handler);
            else
                notificationHandlers.Add(key, handler);
        }

        public void AddNotificationHandler<T>(Dispatcher dispatcher, NotificationHandler<T> handler)
        {
            AddNotificationHandler<T>(new NotificationHandler<T>(delegate(T message)
            {
                dispatcher.BeginInvoke(new SyncInvoker(delegate()
                {
                    handler(message);
                }));
            }));
        }

        public void AddTransactionHandler<TRequest, TState>(
            PrepareHandler<TRequest, TState> prepare,
            CommitRollbackHandler<TState> commit,
            CommitRollbackHandler<TState> rollback) 
            where TState : TransactionState<TRequest>
            where TRequest : Message
        {
            Type key = typeof(TRequest);
            TransactionHandler handler = new TransactionHandler(prepare, commit, rollback);
            if (transactionHandlers.ContainsKey(key))
            {
                transactionHandlers[key] = handler;
                Trace.WriteLine("Transaction handler for " + key.Name + " is replaced");
            }
            else
                transactionHandlers.Add(key, handler);
        }

        public void AddTransactionHandler<TRequest, TState>(
            Dispatcher dispatcher,
            AsyncPrepareHandler<TRequest, TState> prepare,
            CommitRollbackHandler<TState> commit,
            CommitRollbackHandler<TState> rollback) 
            where TState : TransactionState<TRequest>
            where TRequest : Message
        {
            AddTransactionHandler<TRequest, TState>(
                new PrepareHandler<TRequest, TState>(delegate(TRequest request)
                {
                    HandlerResult<TState> result = new HandlerResult<TState>();
                    dispatcher.BeginInvoke(new SyncInvoker(delegate()
                    {
                        try
                        {
                            prepare(request, result);
                        }
                        catch (Exception exc)
                        {
                            result.SetFailure(exc);
                        }
                    }));
                    result.WaitForResult();
                    if (result.IsSuccess)
                        return result.Value;
                    else
                        throw result.Fault;
                }),
                new CommitRollbackHandler<TState>(delegate(TState state)
                {
                    dispatcher.BeginInvoke(new SyncInvoker(delegate()
                    {
                        commit(state);
                    }));
                }),
                new CommitRollbackHandler<TState>(delegate(TState state)
                {
                    dispatcher.BeginInvoke(new SyncInvoker(delegate()
                    {
                       rollback(state);
                    }));
                }));
        }

        /// <summary>Sends message to specified nodes</summary>
        /// <param name="nodes">Array of node ids</param>
        /// <param name="request">Message to send</param>
        /// <returns>Array of responses. Number of responses equals number of nodes</returns>
        public abstract Response[] Send(int[] nodes,Message request);

        public Response[] Send(Message request)
        {
            return Send(AllNodes, request);
        }

        /// <summary>Sends message to specified nodes</summary>
        /// <param name="nodes">Array of node ids</param>
        /// <param name="request">Message to send</param>
        /// <remarks>Does not receive any result</remarks>
        public abstract void Notify(int[] nodes, Message request);

        public void Notify(Message request)
        {
            Notify(AllNodes, request);
        }

        /// <summary>Performs specified request in transaction mode on all nodes</summary>
        /// <param name="request">Request to perform</param>
        public abstract void Perform(Message request);

        /// <summary>Method is invoked when message arrives</summary>
        /// <param name="sender">Id of node that sends message</param>
        /// <param name="request">Message itself</param>
        /// <returns>Message processing result</returns>
        public virtual object HandleMessage(Message request)
        {
            Delegate handler;
            if (messageHandlers.TryGetValue(request.GetType(), out handler))
                return handler.DynamicInvoke(request);
            Trace.WriteLine("MultiViewer.Node: no handler is found for message type " + request.GetType().Name);
            return null;
        }

        /// <summary>Method is invoked when notification arrives</summary>
        /// <param name="sender">Id of node that sends message</param>
        /// <param name="request">Message itself</param>
        public virtual void HandleNotification(Message request)
        {
            Delegate handler;
            if (notificationHandlers.TryGetValue(request.GetType(), out handler))
            {
                handler.DynamicInvoke(request);
                return;
            }
            Trace.WriteLine("MultiViewer.Node: no handler is found for notification type " + request.GetType().Name);
        }

        public virtual bool HandlePrepare(string id, Message request)
        {
            lock (transactions)
            {
                //Thread.Sleep(100);
                if (transactions.ContainsKey(id))
                    throw new InvalidOperationException("Transaction " + id + " is already ongoing");
                else
                    transactions.Add(id, null);
            }
            TransactionHandler handler;
            if (transactionHandlers.TryGetValue(request.GetType(), out handler)) {
                ITransactionState state = handler.Prepare(request);
                lock(transactions)
                    if(transactions.ContainsKey(id))
                        transactions[id] = state;
                return true;
            } else {
                Trace.WriteLine("MultiViewer.Node: no handler is found for transaction type " + request.GetType().Name);
                return false;
            }
        }

        public virtual void HandleCommit(string id)
        {
            ITransactionState state = null;
            lock(transactions) {
                if (!transactions.ContainsKey(id))
                    throw new InvalidOperationException("Transaction " + id + " is not found");
                state = transactions[id];
                transactions.Remove(id);
            }
            TransactionHandler handler;
            if (transactionHandlers.TryGetValue(state.Request.GetType(), out handler))
                handler.Commit(state);
            else 
                Trace.WriteLine("MultiViewer.Node: no handler is found for committing transaction type " + state.Request.GetType().Name);
        }

        public virtual void HandleRollback(string id)
        {
            ITransactionState state = null;
            lock (transactions)
            {
                if (!transactions.ContainsKey(id))
                    throw new InvalidOperationException("Transaction " + id + " is not found");
                state = transactions[id];
                transactions.Remove(id);
            }
            TransactionHandler handler;
            if (transactionHandlers.TryGetValue(state.Request.GetType(), out handler))
                handler.Rollback(state);
            else
                Trace.WriteLine("MultiViewer.Node: no handler is found for rolling back transaction type " + state.Request.GetType().Name);
        }

        protected virtual void OnNodeConnected(int nodeId)
        {
            if (NodeConnected != null)
                NodeConnected(nodeId);
        }

        protected virtual void OnNodeDisconnected(int nodeId)
        {
            if (NodeDisconnected != null)
                NodeDisconnected(nodeId);
        }
    }
}